<?php
/**
 * Created by PhpStorm.
 * @author WYZ <wyz@jungo.com.cn>
 * @copyright 深圳市俊网网络有限公司
 */

namespace Selibra\Model\Db;


use Co\Channel;
use RuntimeException;
use Selibra\Config\Config;
use Selibra\Tools\Console;
use Selibra\Model\Build\ConfigBuilder;
use Selibra\ORM\Exceptions\ConnectFailedException;
use Swoole\Coroutine;
use Swoole\Coroutine\MySQL;
use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool;
use Swoole\Lock;


/**
 * mysql连接池，参考于 https://www.cnblogs.com/xinliangcoder/p/10930035.html
 * Class MysqlPool
 * @package Selibra\Model\Db
 */
class MysqlPool
{

    /**
     * 连接池
     * @var PDOPool
     */
    protected PDOPool $pool;


    /**
     * @var $this
     */
    protected static self $ins;


    /**
     * @return MysqlPool|static
     * @throws ConnectFailedException
     */
    public static function getIns(ConfigBuilder $config = null)
    {
        if (!isset(self::$ins)) {
            self::$ins = new self();
            self::$ins->genConnects($config);
        }
        return self::$ins;
    }


    /**
     * MysqlPool constructor.
     * @param ConfigBuilder|null $config
     * @throws ConnectFailedException
     */
    public function __construct()
    {

    }


    /**
     * @param ConfigBuilder|null $config
     * @throws ConnectFailedException
     */
    public function genConnects(ConfigBuilder $config = null)
    {
        // 生成连接池
        if (empty($config)) {
            $config = new ConfigBuilder();
        }
        if (!isset($this->pool)) {
            $configs = $config->getConfigs();
            $pdoConfig = new PDOConfig();
            $pdoConfig->withHost($configs['host'])
                ->withPort($configs['port'])
                ->withDbName($configs['database'])
                ->withCharset($configs['charset'] ?? 'utf8mb4')
                ->withUsername($configs['user'])
                ->withPassword($configs['password']);
            $this->pool = new PDOPool($pdoConfig, $configs['pool_size']);
        }
    }


    /**
     * @deprecated
     */
    public function getNewConnect($configs)
    {
        $dsn = "mysql:host={$configs['host']};port={$configs['port']};dbname={$configs['database']}";
        $pdo = new \PDO($dsn, $configs['user'], $configs['password'], [
            \PDO::ATTR_PERSISTENT => true
        ]);

        return $pdo;
    }


    /**
     * @return mixed
     */
    public function get($time = 1)
    {
        $mysql = $this->pool->get();

        if (false === $mysql) {
            throw new RuntimeException("Pop mysql timeout");
        }
        // 检测连接是否可用
        try {
            $result = $mysql->getAttribute(\PDO::ATTR_SERVER_INFO);
            if (empty($result)) {
                throw new \PDOException("连接失败!");
            } elseif ($result == 'MySQL server has gone away') {
                throw new \PDOException("连接失败!");
            }
        } catch (\PDOException $exception) {
            if ($time < 3) {
                // 推一个新的MySQL连接进去
                go(function () use ($mysql) {
                    $mysql->reconnect();
                    $this->pool->put($mysql);
                });
                // 重新取一个连接池
                return $this->get($time++);
            }
            // 重启连接
            throw new \PDOException("连接失败!");
        }
        // 协程结束，释放连接
        defer(function () use ($mysql) { // 释放
            $this->pool->put($mysql);
        });
        return $mysql;
    }


    /**
     * @param $mysql
     * @deprecated
     */
    public function verifyAvailabilityRestart($mysql)
    {
        if (empty($mysql)) {
            $config = new ConfigBuilder();
            $mysql = $this->get();
            return $mysql;
        }
        try {
            $result = $mysql->getAttribute(\PDO::ATTR_SERVER_INFO);
            if (empty($result)) {
                throw new \PDOException("连接失败!");
            } elseif ($result == 'MySQL server has gone away') {
                throw new \PDOException("连接失败!");
            }
        } catch (\PDOException $exception) {
            // 重启连接
            $config = new ConfigBuilder();
            $mysql = $this->getNewConnect($config->getConfigs());
        }
        return $mysql;
    }

}
